package com.wdd.myplatform.controller.esayexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.merge.LoopMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.wdd.myplatform.entity.DemoMergeData;
import com.wdd.myplatform.entity.ImageDemoData;
import com.wdd.myplatform.entity.TEasyexcel;
import com.wdd.myplatform.service.TEasyexcelService;
import com.wdd.myplatform.utils.TestFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author wdd 2022/6/9
 */
@RequestMapping("/easyExcelController")
@RestController
@Slf4j
public class EasyExcelController {

    @Autowired
    private TEasyexcelService tEasyexcelService;

    // simpleRead有三种实现方式，可根据实际需求任选一种调用，EasyExcel.read方法可接受的文件参数可以是路径、File、inputStream，
    //下面三种方法将分别以三种类型的入参做测试


    /**
     * 最简单的读
     * // 写法1：JDK8+ ,不用额外写一个DemoDataListener
     * <p>1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>2. 由于默认一行行的读取excel，所以需要创建excel一行一行的回调监听器，参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @GetMapping("/simpleRead1")
    public void simpleRead() {
        String fileName = TestFileUtil.getPath() + "easyexcel" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet 文件流会自动关闭
        // 这里每次会读取100条数据 然后返回过来 直接调用使用数据就行
        EasyExcel.read(fileName, TEasyexcel.class, new PageReadListener<TEasyexcel>(dataList -> {
            for (TEasyexcel tEasyexcel : dataList) {
                log.info("读取到一条数据{}", JSON.toJSONString(tEasyexcel));
                tEasyexcelService.save(tEasyexcel);
            }
        })).sheet().doRead();
    }

    /**
     * 最简单的读
     * 写法2：
     * 匿名内部类 不用额外写一个DemoDataListener
     * <p>1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>2. 由于默认一行行的读取excel，所以需要创建excel一行一行的回调监听器，参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @GetMapping("/simpleRead2")
    public void simpleRead2() {
        String fileName = TestFileUtil.getPath() + "easyexcel" + File.separator + "demo.xlsx";
        File file = new File(fileName);
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(file, TEasyexcel.class, new ReadListener<TEasyexcel>() {
            /**
             * 单次缓存的数据量
             */
            public static final int BATCH_COUNT = 100;
            /**
             *临时存储
             */
            private List<TEasyexcel> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

            @Override
            public void invoke(TEasyexcel data, AnalysisContext context) {
                cachedDataList.add(data);
                if (cachedDataList.size() >= BATCH_COUNT) {
                    saveData();
                    // 存储完成清理 list
                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                saveData();
            }

            /**
             * 加上存储数据库
             */
            private void saveData() {
                log.info("{}条数据，开始存储数据库！", cachedDataList.size());
                tEasyexcelService.saveBatch(cachedDataList);
                log.info("存储数据库成功！");
            }
        }).sheet().doRead();
    }

    /**
     * 最简单的读
     * 写法3：
     * 有个很重要的点 DemoDataListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
     * <p>1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>2. 由于默认一行行的读取excel，所以需要创建excel一行一行的回调监听器，参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @GetMapping("/simpleRead3")
    public void simpleRead3() throws Exception {
        String fileName = TestFileUtil.getPath() + "easyexcel" + File.separator + "demo.xlsx";
        FileInputStream fileInputStream = FileUtils.openInputStream(new File(fileName));
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileInputStream, TEasyexcel.class, new DemoDataListener(tEasyexcelService)).sheet().doRead();
    }

    /**
     * 最简单的读
     * 写法4：
     * 一个文件一个reader
     * <p>1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>2. 由于默认一行行的读取excel，所以需要创建excel一行一行的回调监听器，参照{@link DemoDataListener}
     * <p>3. 直接读即可
     */
    @GetMapping("/simpleRead4")
    public void simpleRead4() throws Exception {
        String fileName = TestFileUtil.getPath() + "easyexcel" + File.separator + "demo.xlsx";
        // 一个文件一个reader
        try (ExcelReader excelReader = EasyExcel.read(fileName, TEasyexcel.class, new DemoDataListener(tEasyexcelService)).build()) {
            // 构建一个sheet 这里可以指定名字或者no
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            // 读取一个sheet
            excelReader.read(readSheet);
        }
    }

    /**
     * EasyExcel.write方法可接受的文件参数可以是路径、File、inputStream，
     * 注意 simpleWrite在数据量不大的情况下可以使用（5000以内，具体也要看实际情况），数据量大参照 重复多次写入
     * 写法1 JDK8+
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{com.alibaba.easyexcel.test.demo.write.DemoData}
     * <p>2. 直接写即可
     */
    @GetMapping("/simpleWrite1")
    public void simpleWrite1() {
        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读，然后写到第一个sheet，名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, TEasyexcel.class)
                .sheet("模板")
                .doWrite(() -> {
                    // 分页查询数据
                    return data();
                });
    }

    /**
     * 写法2 JDK8+
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{com.alibaba.easyexcel.test.demo.write.DemoData}
     * <p>2. 直接写即可
     */
    @GetMapping("/simpleWrite2")
    public void simpleWrite2() throws Exception{
        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读，然后写到第一个sheet，名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        File file = new File(fileName);
        if(!file.exists()){
            file.createNewFile();
        }
        EasyExcel.write(file, TEasyexcel.class).sheet("模板").doWrite(data());
    }

    /**
     * 写法3
     * 最简单的写
     * <p>1. 创建excel对应的实体对象 参照{com.alibaba.easyexcel.test.demo.write.DemoData}
     * <p>2. 直接写即可
     */
    @GetMapping("/simpleWrite3")
    public void simpleWrite3(HttpServletRequest request,HttpServletResponse response) throws IOException {
        String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        File file = new File(fileName);
        OutputStream outputStream = FileUtils.openOutputStream(file);
        try (ExcelWriter excelWriter = EasyExcel.write(outputStream, TEasyexcel.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        }
    }

    /**
     * 重复多次写入
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>
     * 2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>
     * 3. 直接调用二次写入即可
     */
    @GetMapping("/repeatedWrite")
    public void repeatedWrite() {
        // 方法1: 如果写到同一个sheet
        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, TEasyexcel.class).build()) {
            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            // 去调用写入,这里我调用了五次，实际使用时根据数据库分页的总的页数来
            for (int i = 0; i < 5; i++) {
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<TEasyexcel> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法2: 如果写到不同的sheet 同一个对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, TEasyexcel.class).build()) {
            // 去调用写入,这里我调用了五次，实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<TEasyexcel> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法3 如果写到不同的sheet 不同的对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
            // 去调用写入,这里我调用了五次，实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变，我这里为了方便 所以用的同一个class
                // 实际上可以一直变
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(TEasyexcel.class).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<TEasyexcel> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

    }

    /**
     * 图片导出
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link ImageDemoData}
     * <p>
     * 2. 直接写即可
     */
    @GetMapping("/imageWrite")
    public void imageWrite(HttpServletResponse response) throws Exception {
        String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
       /* response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");*/

        String imagePath = TestFileUtil.getPath() + "easyexcel" + File.separator + "img.jpg";
        try (InputStream inputStream = com.alibaba.excel.util.FileUtils.openInputStream(new File(imagePath))) {
            List<ImageDemoData> list = new ArrayList<>();
            ImageDemoData imageDemoData = new ImageDemoData();
            // 放入五种类型的图片 实际使用只要选一种即可
            imageDemoData.setByteArray(com.alibaba.excel.util.FileUtils.readFileToByteArray(new File(imagePath)));
            imageDemoData.setFile(new File(imagePath));
            imageDemoData.setString(imagePath);
            imageDemoData.setInputStream(inputStream);
            imageDemoData.setUrl(new URL("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp09%2F21061109241GT3-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1657434830&t=7c280960bd56ab23ef5d25cc997df73f"));
            list.add(imageDemoData);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            // 写入数据
            EasyExcel.write(response.getOutputStream(), ImageDemoData.class).sheet().doWrite(list);
        }
    }

    /**
     * 合并单元格
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link TEasyexcel} {@link DemoMergeData}
     * <p>
     * 2. 创建一个merge策略 并注册
     * <p>
     * 3. 直接写即可
     *
     * @since 2.2.0-beta1
     */
    @GetMapping("/mergeWrite")
    public void mergeWrite() {
        // 方法1 注解
        String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
        // 在DemoStyleData里面加上ContentLoopMerge注解
        // 这里 需要指定写用哪个class去写，然后写到第一个sheet，名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data());

        // 方法2 自定义合并单元格策略
        fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
        // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度，所以就第一列会合并。当然其他合并策略也可以自己写
        LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
        // 这里 需要指定写用哪个class去写，然后写到第一个sheet，名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, TEasyexcel.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());
    }

    /**
     * 文件下载（失败了会返回一个有部分数据的Excel）
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写，这里注意，finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
     */
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), TEasyexcel.class).sheet("模板").doWrite(data());
    }

    /**
     * 文件上传
     * <p>1. 创建excel对应的实体对象 参照{@link TEasyexcel}
     * <p>2. 由于默认一行行的读取excel，所以需要创建excel一行一行的回调监听器，参照{@link UploadDataListener}
     * <p>3. 直接读即可
     */
    @PostMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), TEasyexcel.class, new UploadDataListener(tEasyexcelService)).sheet().doRead();
        return "success";
    }

    private List<TEasyexcel> data() {
        List<TEasyexcel> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            TEasyexcel data = new TEasyexcel();
            data.setStrTitle("字符串" + i);
            data.setDateTitle(new Date());
            data.setDoubleDataTitle(0.56);
            list.add(data);
        }
        return list;
    }

}
